#pragma once
#include <iostream>
#include "OpenCSV.h"
#include "TransferDataChart.h"
#include "GUIState.h"
#include "MyTimer.h"
#include "Diagnostic.h"

// Represents the Main GUI.
//
// Responsible for handling general user interaction, and
// USB transactions.

namespace CC3501_Main_GUI {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;
	using namespace System::Diagnostics;
	using namespace System::IO;
	using namespace System::Threading;
	using namespace std;

	///
	/// USB CONFIG
	///

	// EP3 command set
	static const unsigned char Send_Data        = 0x10;  //EP3 command to send a byte
	static const unsigned char Get_Data         = 0x20;  //EP3 command to get a byte
	static const unsigned char USB_Connected    = 0x30;  //EP3 command to notify MCU we are aware of connection
	static const unsigned char USB_Disconnected = 0x40;  //EP3 command to notify MCU we are disconnecting
	static const unsigned char Run              = 0x70;  //EP3 command to start MCU running
	static const unsigned char Stop             = 0x80;  //EP3 command to stop MCU running
	static const unsigned char Reset_Data       = 0x90;  //EP3 command to reset MCU state
	static const unsigned char Pause            = 0xA0;  //EP3 command to check if it is time to send data to the MCU
	static const unsigned char Status           = 0xB0;  //EP3 command to synchronize the MCU state with the GUI

	static const unsigned int min_sleep_time_between_cmds = 0;  // in ms

	/// <summary>
	/// Summary for MainGUI
	/// </summary>
	public ref class MainGUI : public System::Windows::Forms::Form
	{
	private: System::Windows::Forms::Label^  timerLabel;
	private: System::Windows::Forms::Button^  helpButton;
	private: System::Windows::Forms::Panel^  deviceIDPanel;
	private: System::Windows::Forms::Panel^  connectionStatusPanel;
	private: System::Windows::Forms::Label^  connectionStatusLabel;
	private: System::Windows::Forms::Label^  connectionStatusHeadingLabel;
	private: System::Windows::Forms::Button^  dataTransferGraphButton;
	private: System::Windows::Forms::Panel^  panel4;
	private: System::Windows::Forms::Button^  dataExportButton;
	private: System::Windows::Forms::Button^  fileImportBttn;
	private: ::SimpleUSB::SimpleUSB^  JM60;



	private: System::Windows::Forms::Button^  connectDisconnectButton;
	private: System::Windows::Forms::Button^  resetButton;
	private: System::Windows::Forms::Label^  isCalibratedLabel;
	private: System::Windows::Forms::Label^  calibrationStatusLabel;
	private: System::Windows::Forms::ComboBox^  modeOfOperationSelector;
	private: System::Windows::Forms::SaveFileDialog^  exportCSVFileDialog;
	private: System::Windows::Forms::Button^  exitBttn;			 

	public:
		// Constructor
		//
		// Takes a GUI State object (State Model) as an input
		MainGUI(GUIState^ newState)
		{
			// store a reference to the state
			state = newState;

			// Initialise our Timer
			InitTimer();
			InitializeComponent();
			
			read = false;
			connected = false;
			sentRunning = false;
			tickTracker = 0;
			ticking = false;
			this->modeOfOperationSelector->SelectedIndex = this->modeOfOperationSelector->FindStringExact("Calibration Mode");
			
			// Run the background worker (responsbile for handling general USB transactions)
			this->backgroundWorker->RunWorkerAsync();
		}

		// We need to describe a delegate.. (this is the delegate for our UpDateTimer calls)
		// C++/CLI feature
		delegate void HandleTimerTickDelegate(void);

		// Handle the timer tick
		void Tick(void)
		{
			// If we are running, check if we have reached the end of the file, and have read the entire output buffer.
			// If we aren't calibrate, automatically calibrate when we reach the end of the file.
			if ( this->state->isRunning() ) {
				if ( (this->state->hasReachedEndOfFile()) && (this->state->getOutBufferUsage() == 0) && (this->state->getInBufferUsage() == 0) ) {
					this->pauseRunning();
					
					if (!this->state->isCalibrated()) {
						this->isCalibratedLabel->Text = "Calibrated";
						this->isCalibratedLabel->ForeColor = System::Drawing::Color::SteelBlue;
						this->state->performCalibration();
						this->modeOfOperationSelector->SelectedIndex = this->modeOfOperationSelector->FindStringExact("Run Mode");
						this->state->setNormalMode();
					}
				}
			}

			// Update the GUI timer
			this->UpdateTimer();			
		}

		// Send a pyload, returns how many bytes were sent.
		int SendPayload(void) {
			// If the In Buffer can't fit data, don't send
			if ( this->state->getInBufferSpace() < 64 ) {
				return 0;
			}
			// If we reached the end of the file, don't send
			if ( this->state->hasReachedEndOfFile() ) {
				return 0;
			}

			List<unsigned short>^ dataToSend = gcnew List<unsigned short>();
			UInt16^ data = this->state->readFirstColumnOfNextRowAsUInt16();

			int i = 0;

			while (data != nullptr) {
				// Determine the "goal" data (from our CSV)
				UInt16 dataLit = (unsigned short) data;

				// scale the shaker data based on out calibration factors
				UInt16 dataShakerALitMod = ( (float) dataLit * this->state->getCalibrationFactorShakerA() );
				UInt16 dataShakerBLitMod = ( (float) dataLit * this->state->getCalibrationFactorShakerB() );

				// determine the next index to send
				UInt16 index = this->state->getNextDataIndex();
				dataToSend->Add(index);
				dataToSend->Add(dataShakerALitMod);
				dataToSend->Add(dataShakerBLitMod);

				// Record the sent data
				SentDataRecord^ sentData = gcnew SentDataRecord(index, dataLit, dataShakerALitMod, dataShakerBLitMod);
				this->state->addSentRecord(sentData);

				i++;
				if (i < 10) {
					data = this->state->readFirstColumnOfNextRowAsUInt16();
				} else {
					data = nullptr;
				}
			}

			// Send the data
			this->SendData(dataToSend); 
			return (dataToSend->Count * 2);
		}

		// Read the data from the MCU
		int ReceivePayload(void) {
			return this->GetData();
		}

		// The function to run when our timer ticks
		void UpdateTimer(void)
		{
			this->timerLabel->Text = this->state->getTimer()->getTimeString();
		}

	protected:
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		~MainGUI()
		{  
			if (components)
			{
				delete components;
			}
		}
private: System::Windows::Forms::Button^  diagnosticForm;
protected: 


	private: System::Windows::Forms::Label^  label1;
	private: System::Windows::Forms::Panel^  panel1;
	private: System::Windows::Forms::Button^  startAndResumeButton;

	private: System::Windows::Forms::Label^  simTimerLbl;
	private: System::ComponentModel::IContainer^  components;

	private:
		/// <summary>
		/// Required designer variable.
		/// </summary>

		GUIState^ state;
		System::Boolean connected;
		System::Boolean read;
		System::Boolean sentRunning;
		int tickTracker;
		bool ticking;
		System::ComponentModel::BackgroundWorker^ backgroundWorker;


#pragma region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		void InitializeComponent(void)
		{
			this->backgroundWorker = (gcnew System::ComponentModel::BackgroundWorker());
			this->diagnosticForm = (gcnew System::Windows::Forms::Button());
			this->label1 = (gcnew System::Windows::Forms::Label());
			this->panel1 = (gcnew System::Windows::Forms::Panel());
			this->exitBttn = (gcnew System::Windows::Forms::Button());
			this->fileImportBttn = (gcnew System::Windows::Forms::Button());
			this->dataExportButton = (gcnew System::Windows::Forms::Button());
			this->dataTransferGraphButton = (gcnew System::Windows::Forms::Button());
			this->helpButton = (gcnew System::Windows::Forms::Button());
			this->startAndResumeButton = (gcnew System::Windows::Forms::Button());
			this->simTimerLbl = (gcnew System::Windows::Forms::Label());
			this->timerLabel = (gcnew System::Windows::Forms::Label());
			this->deviceIDPanel = (gcnew System::Windows::Forms::Panel());
			this->calibrationStatusLabel = (gcnew System::Windows::Forms::Label());
			this->isCalibratedLabel = (gcnew System::Windows::Forms::Label());
			this->connectionStatusPanel = (gcnew System::Windows::Forms::Panel());
			this->connectDisconnectButton = (gcnew System::Windows::Forms::Button());
			this->connectionStatusLabel = (gcnew System::Windows::Forms::Label());
			this->connectionStatusHeadingLabel = (gcnew System::Windows::Forms::Label());
			this->panel4 = (gcnew System::Windows::Forms::Panel());
			this->JM60 = (gcnew ::SimpleUSB::SimpleUSB());
			this->resetButton = (gcnew System::Windows::Forms::Button());
			this->modeOfOperationSelector = (gcnew System::Windows::Forms::ComboBox());
			this->exportCSVFileDialog = (gcnew System::Windows::Forms::SaveFileDialog());
			this->panel1->SuspendLayout();
			this->deviceIDPanel->SuspendLayout();
			this->connectionStatusPanel->SuspendLayout();
			this->panel4->SuspendLayout();
			this->SuspendLayout();
			// 
			// backgroundWorker
			// 
			this->backgroundWorker->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &MainGUI::backgroundWorker_DoWork);
			// 
			// diagnosticForm
			// 
			this->diagnosticForm->Location = System::Drawing::Point(12, 67);
			this->diagnosticForm->Name = L"diagnosticForm";
			this->diagnosticForm->Size = System::Drawing::Size(144, 25);
			this->diagnosticForm->TabIndex = 0;
			this->diagnosticForm->Text = L"Diagnostics";
			this->diagnosticForm->UseVisualStyleBackColor = true;
			this->diagnosticForm->Click += gcnew System::EventHandler(this, &MainGUI::openDiagnosticsFormButoon);
			// 
			// label1
			// 
			this->label1->AutoSize = true;
			this->label1->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 10));
			this->label1->Location = System::Drawing::Point(9, 16);
			this->label1->Name = L"label1";
			this->label1->Size = System::Drawing::Size(130, 17);
			this->label1->TabIndex = 1;
			this->label1->Text = L"Additional Features";
			// 
			// panel1
			// 
			this->panel1->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
			this->panel1->Controls->Add(this->exitBttn);
			this->panel1->Controls->Add(this->fileImportBttn);
			this->panel1->Controls->Add(this->dataExportButton);
			this->panel1->Controls->Add(this->dataTransferGraphButton);
			this->panel1->Controls->Add(this->helpButton);
			this->panel1->Controls->Add(this->label1);
			this->panel1->Controls->Add(this->diagnosticForm);
			this->panel1->Location = System::Drawing::Point(352, 23);
			this->panel1->Name = L"panel1";
			this->panel1->Size = System::Drawing::Size(178, 231);
			this->panel1->TabIndex = 2;
			// 
			// exitBttn
			// 
			this->exitBttn->Location = System::Drawing::Point(12, 197);
			this->exitBttn->Name = L"exitBttn";
			this->exitBttn->Size = System::Drawing::Size(144, 25);
			this->exitBttn->TabIndex = 10;
			this->exitBttn->Text = L"Exit";
			this->exitBttn->UseVisualStyleBackColor = true;
			this->exitBttn->Click += gcnew System::EventHandler(this, &MainGUI::exitBttn_Click);
			// 
			// fileImportBttn
			// 
			this->fileImportBttn->Location = System::Drawing::Point(12, 36);
			this->fileImportBttn->Name = L"fileImportBttn";
			this->fileImportBttn->Size = System::Drawing::Size(144, 25);
			this->fileImportBttn->TabIndex = 9;
			this->fileImportBttn->Text = L"Data Import";
			this->fileImportBttn->UseVisualStyleBackColor = true;
			this->fileImportBttn->Click += gcnew System::EventHandler(this, &MainGUI::fileImportBttn_Click);
			// 
			// dataExportButton
			// 
			this->dataExportButton->Location = System::Drawing::Point(12, 129);
			this->dataExportButton->Name = L"dataExportButton";
			this->dataExportButton->Size = System::Drawing::Size(144, 25);
			this->dataExportButton->TabIndex = 9;
			this->dataExportButton->Text = L"Data Export";
			this->dataExportButton->UseVisualStyleBackColor = true;
			this->dataExportButton->Click += gcnew System::EventHandler(this, &MainGUI::exportCSVButton_Click);
			// 
			// dataTransferGraphButton
			// 
			this->dataTransferGraphButton->Location = System::Drawing::Point(12, 98);
			this->dataTransferGraphButton->Name = L"dataTransferGraphButton";
			this->dataTransferGraphButton->Size = System::Drawing::Size(144, 25);
			this->dataTransferGraphButton->TabIndex = 8;
			this->dataTransferGraphButton->Text = L"Data Transfer Graph";
			this->dataTransferGraphButton->UseVisualStyleBackColor = true;
			this->dataTransferGraphButton->Click += gcnew System::EventHandler(this, &MainGUI::dataTransferGraphButton_Click);
			// 
			// helpButton
			// 
			this->helpButton->Location = System::Drawing::Point(12, 166);
			this->helpButton->Name = L"helpButton";
			this->helpButton->Size = System::Drawing::Size(144, 25);
			this->helpButton->TabIndex = 6;
			this->helpButton->Text = L"Help";
			this->helpButton->UseVisualStyleBackColor = true;
			this->helpButton->Click += gcnew System::EventHandler(this, &MainGUI::helpButton_Click);
			// 
			// startAndResumeButton
			// 
			this->startAndResumeButton->Enabled = false;
			this->startAndResumeButton->Location = System::Drawing::Point(17, 223);
			this->startAndResumeButton->Name = L"startAndResumeButton";
			this->startAndResumeButton->Size = System::Drawing::Size(73, 34);
			this->startAndResumeButton->TabIndex = 3;
			this->startAndResumeButton->Text = L"Start";
			this->startAndResumeButton->UseVisualStyleBackColor = true;
			this->startAndResumeButton->Click += gcnew System::EventHandler(this, &MainGUI::startPause_Click);
			// 
			// simTimerLbl
			// 
			this->simTimerLbl->AutoSize = true;
			this->simTimerLbl->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 10));
			this->simTimerLbl->Location = System::Drawing::Point(2, 3);
			this->simTimerLbl->Name = L"simTimerLbl";
			this->simTimerLbl->Size = System::Drawing::Size(163, 17);
			this->simTimerLbl->TabIndex = 4;
			this->simTimerLbl->Text = L"Elapsed Simulation Time";
			// 
			// timerLabel
			// 
			this->timerLabel->AutoSize = true;
			this->timerLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12));
			this->timerLabel->Location = System::Drawing::Point(45, 25);
			this->timerLabel->Name = L"timerLabel";
			this->timerLabel->Size = System::Drawing::Size(71, 20);
			this->timerLabel->TabIndex = 5;
			this->timerLabel->Text = L"00:00:00";
			// 
			// deviceIDPanel
			// 
			this->deviceIDPanel->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
			this->deviceIDPanel->Controls->Add(this->calibrationStatusLabel);
			this->deviceIDPanel->Controls->Add(this->isCalibratedLabel);
			this->deviceIDPanel->Location = System::Drawing::Point(17, 114);
			this->deviceIDPanel->Name = L"deviceIDPanel";
			this->deviceIDPanel->Size = System::Drawing::Size(328, 71);
			this->deviceIDPanel->TabIndex = 6;
			// 
			// calibrationStatusLabel
			// 
			this->calibrationStatusLabel->AutoSize = true;
			this->calibrationStatusLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 10));
			this->calibrationStatusLabel->Location = System::Drawing::Point(3, 0);
			this->calibrationStatusLabel->Name = L"calibrationStatusLabel";
			this->calibrationStatusLabel->Size = System::Drawing::Size(119, 17);
			this->calibrationStatusLabel->TabIndex = 10;
			this->calibrationStatusLabel->Text = L"Calibration Status";
			// 
			// isCalibratedLabel
			// 
			this->isCalibratedLabel->AutoSize = true;
			this->isCalibratedLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 20.25F, System::Drawing::FontStyle::Regular, 
				System::Drawing::GraphicsUnit::Point, static_cast<System::Byte>(0)));
			this->isCalibratedLabel->ForeColor = System::Drawing::Color::Firebrick;
			this->isCalibratedLabel->Location = System::Drawing::Point(117, 26);
			this->isCalibratedLabel->Name = L"isCalibratedLabel";
			this->isCalibratedLabel->Size = System::Drawing::Size(188, 31);
			this->isCalibratedLabel->TabIndex = 9;
			this->isCalibratedLabel->Text = L"Not Calibrated";
			// 
			// connectionStatusPanel
			// 
			this->connectionStatusPanel->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
			this->connectionStatusPanel->Controls->Add(this->connectDisconnectButton);
			this->connectionStatusPanel->Controls->Add(this->connectionStatusLabel);
			this->connectionStatusPanel->Controls->Add(this->connectionStatusHeadingLabel);
			this->connectionStatusPanel->Location = System::Drawing::Point(17, 23);
			this->connectionStatusPanel->Name = L"connectionStatusPanel";
			this->connectionStatusPanel->Size = System::Drawing::Size(328, 85);
			this->connectionStatusPanel->TabIndex = 7;
			// 
			// connectDisconnectButton
			// 
			this->connectDisconnectButton->Location = System::Drawing::Point(16, 28);
			this->connectDisconnectButton->Name = L"connectDisconnectButton";
			this->connectDisconnectButton->Size = System::Drawing::Size(80, 31);
			this->connectDisconnectButton->TabIndex = 9;
			this->connectDisconnectButton->Text = L"Connect";
			this->connectDisconnectButton->UseVisualStyleBackColor = true;
			this->connectDisconnectButton->Click += gcnew System::EventHandler(this, &MainGUI::connectDisconnectButton_Click);
			// 
			// connectionStatusLabel
			// 
			this->connectionStatusLabel->AutoSize = true;
			this->connectionStatusLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 20));
			this->connectionStatusLabel->ForeColor = System::Drawing::Color::Firebrick;
			this->connectionStatusLabel->Location = System::Drawing::Point(117, 28);
			this->connectionStatusLabel->Name = L"connectionStatusLabel";
			this->connectionStatusLabel->Size = System::Drawing::Size(180, 31);
			this->connectionStatusLabel->TabIndex = 8;
			this->connectionStatusLabel->Text = L"Disconnected";
			// 
			// connectionStatusHeadingLabel
			// 
			this->connectionStatusHeadingLabel->AutoSize = true;
			this->connectionStatusHeadingLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 10));
			this->connectionStatusHeadingLabel->Location = System::Drawing::Point(3, 0);
			this->connectionStatusHeadingLabel->Name = L"connectionStatusHeadingLabel";
			this->connectionStatusHeadingLabel->Size = System::Drawing::Size(0, 17);
			this->connectionStatusHeadingLabel->TabIndex = 10;
			// 
			// panel4
			// 
			this->panel4->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
			this->panel4->Controls->Add(this->simTimerLbl);
			this->panel4->Controls->Add(this->timerLabel);
			this->panel4->Location = System::Drawing::Point(178, 194);
			this->panel4->Name = L"panel4";
			this->panel4->Size = System::Drawing::Size(167, 63);
			this->panel4->TabIndex = 8;
			// 
			// JM60
			// 
			this->JM60->GUID = System::Guid(L"8fa1a550-7849-4619-905c-da105b3a3959");
			this->JM60->onDeviceConnect += gcnew SimpleUSB::SimpleUSB::onDeviceConnectEventHandler(this, &MainGUI::JM60_onDeviceConnect);
			this->JM60->onDeviceDisconnect += gcnew SimpleUSB::SimpleUSB::onDeviceDisconnectEventHandler(this, &MainGUI::JM60_onDeviceDisconnect);
			// 
			// resetButton
			// 
			this->resetButton->Enabled = false;
			this->resetButton->Location = System::Drawing::Point(96, 223);
			this->resetButton->Name = L"resetButton";
			this->resetButton->Size = System::Drawing::Size(76, 34);
			this->resetButton->TabIndex = 9;
			this->resetButton->Text = L"Reset";
			this->resetButton->UseVisualStyleBackColor = true;
			this->resetButton->Click += gcnew System::EventHandler(this, &MainGUI::resetButton_Click);
			// 
			// modeOfOperationSelector
			// 
			this->modeOfOperationSelector->DropDownStyle = System::Windows::Forms::ComboBoxStyle::DropDownList;
			this->modeOfOperationSelector->Enabled = false;
			this->modeOfOperationSelector->FormattingEnabled = true;
			this->modeOfOperationSelector->Items->AddRange(gcnew cli::array< System::Object^  >(2) {L"Calibration Mode", L"Run Mode"});
			this->modeOfOperationSelector->Location = System::Drawing::Point(17, 194);
			this->modeOfOperationSelector->Name = L"modeOfOperationSelector";
			this->modeOfOperationSelector->Size = System::Drawing::Size(155, 21);
			this->modeOfOperationSelector->TabIndex = 10;
			this->modeOfOperationSelector->SelectedIndexChanged += gcnew System::EventHandler(this, &MainGUI::modeOfOperationSelector_SelectedIndexChanged);
			// 
			// exportCSVFileDialog
			// 
			this->exportCSVFileDialog->DefaultExt = L"csv";
			this->exportCSVFileDialog->FileOk += gcnew System::ComponentModel::CancelEventHandler(this, &MainGUI::exportCSVFileDialog_FileOk);
			// 
			// MainGUI
			// 
			this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
			this->ClientSize = System::Drawing::Size(541, 268);
			this->Controls->Add(this->modeOfOperationSelector);
			this->Controls->Add(this->resetButton);
			this->Controls->Add(this->panel4);
			this->Controls->Add(this->connectionStatusPanel);
			this->Controls->Add(this->deviceIDPanel);
			this->Controls->Add(this->startAndResumeButton);
			this->Controls->Add(this->panel1);
			this->HelpButton = true;
			this->Name = L"MainGUI";
			this->Text = L"Control Interface";
			this->HelpButtonClicked += gcnew System::ComponentModel::CancelEventHandler(this, &MainGUI::MainGUI_HelpButtonClicked);
			this->panel1->ResumeLayout(false);
			this->panel1->PerformLayout();
			this->deviceIDPanel->ResumeLayout(false);
			this->deviceIDPanel->PerformLayout();
			this->connectionStatusPanel->ResumeLayout(false);
			this->connectionStatusPanel->PerformLayout();
			this->panel4->ResumeLayout(false);
			this->panel4->PerformLayout();
			this->ResumeLayout(false);

		}
#pragma endregion

		// Perform the USB interactions in the NON-GUI thread.
		// By doing the work in the non-gui thread, we ensure that the GUI remains responsive,
		// and that the USB interactions aren't slowed by long GUI interaction processes.
private: void backgroundWorker_DoWork(System::Object ^,System::ComponentModel::DoWorkEventArgs ^)
        {
			// Periodic status update count
			int status_cnt = 0;
            
			while(true) {
				// sleep between runs.
				Thread::Sleep(min_sleep_time_between_cmds);			
				if ( this->state->isRunning() ) {	
					if (status_cnt == 0) {
						// Update the Status (sync the MCU state to the GUI)
						int running_status = this->UpdateStatus();	
						
						if (running_status == 0) {
							// Ensure that the the MCU is in the run state
							this->sendMCURunCommand();	
						}
					} else {

						// Deciding to read/write
						// 
						// don't write, unless there is enough room to move to written data to the output buffer.
						//
						// Read if we don't have room to move the data from the in buffer to the out buffer (with some head room)
						//
						
						// Prioritise writing if the in buffer usage is "low"
						if (this->state->getInBufferSpace() > 2600) {
							read = false;
						}
						
						// Read if there isn't sufficient room to write
						if (this->state->getOutBufferSpace() < ( this->state->getInBufferUsage() + 2600 ) ) {
							read = true;
						}

						// Always read if we have nothing to write
						if (this->state->hasReachedEndOfFile()) {
							read = true;
						}
					
						if (read) {
							// Read from the MCU
							this->ReceivePayload();						
						} else {
							// Write to the MCU
							this->SendPayload();											
						}						
						this->read = !this->read;	
					}

					// Increment the status count
					status_cnt = ( (status_cnt + 1) % 40 );
				} else {
					Thread::Sleep(10);
				}
			}
        }

	private: System::Void InitTimer() {
				 // Add our own handler to when the tick fires..
				 // i.e. call the timer1_Tick_new function on "this" when the myTimer ticks.
				 this->state->getTimer()->addTickHandler(gcnew ElapsedEventHandler(this, &MainGUI::handleTimerTick));
			 }

	// open the diagnostics form
	private: System::Void openDiagnosticsFormButoon(System::Object^  sender, System::EventArgs^  e) {
				 Diagnostic^ form = gcnew Diagnostic(this->state);
				 form->Show();
			 }

	// start/pause the running of the GUI
	private: System::Void startPause_Click(System::Object^  sender, System::EventArgs^  e) {
				 if ( ! this->state->isRunning() ) {
					 this->startResumeRunning();					 
				 }
				 else {
					 this->pauseRunning();					 
				 }				 
			 }

	// Reset the running of the GUI (stop and clear)
	private: System::Void resetButton_Click(System::Object^  sender, System::EventArgs^  e) {
				 this->stopAndResetRunning();
			 }

	private: System::Void helpButton_Click(System::Object^  sender, System::EventArgs^  e) {			
				 this->showHelp();
			 }
	
	private: System::Void dataTransferGraphButton_Click(System::Object^  sender, System::EventArgs^  e) {
				 TransferDataChart^ form = gcnew TransferDataChart(this->state);
				 form->Show();
			 }

	private: System::Void exportCSVButton_Click(System::Object^  sender, System::EventArgs^  e) {
				 this->exportCSVFileDialog->ShowDialog();
			 }

	private: System::Void fileImportBttn_Click(System::Object^  sender, System::EventArgs^  e) {
				 OpenCSV ^ form = gcnew OpenCSV(this->state);
				 form->ShowDialog();
			 }
	private: System::Void exitBttn_Click(System::Object^  sender, System::EventArgs^  e) {
				 Close();
			 }
	
	private: System::Void handleTimerTick(System::Object^  sender, ElapsedEventArgs^  e) {
				 //// Debugger::Log(0, nullptr, ".\n");
				 // run the UpDateTimer function on this...
				 // Note: we say this->Invoke, as we need the GUI thread to execute the timer update.
				 // The Invoke ensures that the delegate function is run by the GUI thread.
				 // If you don't do this, it will exception..
				 // so..
				 // it is running UpDateTimer (via the UpdateTimerNew delegate) on "this" (which is the form)
				 // within the thread of the GUI... and breathe..
				 this->Invoke(gcnew HandleTimerTickDelegate(this, &MainGUI::Tick));

				 // this->Invoke(this->UpdateTimerNew);
			 };

	private: System::Void pauseRunning() {
				 this->state->pause();
				 this->startAndResumeButton->Text = L"Resume";
				 this->sendMCUStopCommand();
				 this->UpdateTimer();
			 }

	private: System::Void startResumeRunning() {
				 this->state->resume();
				 this->startAndResumeButton->Text = L"Pause";
				 this->UpdateTimer();
				 this->modeOfOperationSelector->Enabled = false;
			 }

	private: System::Void stopAndResetRunning() {
				 this->state->stop();
				 this->sendMCUStopCommand();
				 this->resetMCU();
				 this->startAndResumeButton->Text = L"Start";	
				 this->UpdateTimer();
				 this->read = false;

				 // TODO -> If calibrated...
				 if ( this->state->isCalibrated() ) {
					this->modeOfOperationSelector->Enabled = true;
				 }
			 }

			 // Reset the MCU
	private: System::Void resetMCU() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = Reset_Data; 
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // Notify the MCU of connected status
	private: System::Void notifyMCUOfConnectedStatus() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = USB_Connected;
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // Send the MCU the Run command
	private: System::Void sendMCURunCommand() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = Run;
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 this->sentRunning = true;
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // Send the MCU the Stop command
	private: System::Void sendMCUStopCommand() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = Stop;
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 this->sentRunning = false;
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // Send the MCU the Pause command
	private: System::Void sendMCUPauseCommand() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = Pause;
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // Notify the MCU of disconnected status
	private: System::Void notifyMCUOfDisconnectedStatus() {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(1);

				 OutBuffer[0] = USB_Disconnected;
				 JM60->WriteData(3, OutBuffer, 1);	//Send command/data	
				 Thread::Sleep(min_sleep_time_between_cmds);
			 }

			 // The device is now connected
	private: System::Void JM60_onDeviceConnect() {
				 this->startAndResumeButton->Enabled = true;
				 this->resetButton->Enabled = true;

				 this->connected = true;
				 this->connectionStatusLabel->Text = "Connected";
				 this->connectDisconnectButton->Text = L"Disconnect";
				 this->connectionStatusLabel->ForeColor = System::Drawing::Color::SteelBlue;	

				 // reset the state of the GUI
				 this->state->stop();

				 // Notify the MCU we know we are connected
				 this->notifyMCUOfConnectedStatus();

				 // reset the state of the MCU
				 this->resetMCU();

			 }

			 // The device is now disconnected
	private: System::Void JM60_onDeviceDisconnect() {
				 this->startAndResumeButton->Enabled = false;
				 this->resetButton->Enabled = false;

				 // The device is disconnected, so reset the running of the program
				 this->stopAndResetRunning();

				 this->connected = false;
				 this->connectionStatusLabel->Text = "Disconnected";
				 this->connectDisconnectButton->Text = L"Connect";
				 this->connectionStatusLabel->ForeColor = System::Drawing::Color::Firebrick;
			 }

			 //
			 // The user wants to connect/disconnect to/from the device
			 //
	private: System::Void connectDisconnectButton_Click(System::Object^  sender, System::EventArgs^  e) {
				 if (this->connected) {
					 this->notifyMCUOfDisconnectedStatus();
					 JM60->CloseConnection();
				 } else {
					 JM60->OpenConnection();
				 }
			 }

	//Send Data via Bulk trasnfer USB EP 3
	private: System::Void SendData(List<UInt16>^ DataToWrite) {	
				 int packetSize = (DataToWrite->Count * 2) + 2;
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(packetSize);
				 cli::array<unsigned char> ^ OutData   = gcnew cli::array<unsigned char>(packetSize-2);

				 OutBuffer[0] = Send_Data;
				 OutBuffer[1] = ( DataToWrite->Count * 2 );// (DataToWrite->Count * 2); //Data to be sent to MCU				

				 //Array containig Data to be sent to MCU
				 int dest_pos = 0;
				 int i = 0;
				 for each(UInt16 data in DataToWrite){
					 array<unsigned char>^ bytes = BitConverter::GetBytes(data);
					
					 OutData[dest_pos] = bytes[1];
					 dest_pos++;
					 OutData[dest_pos] = bytes[0];
					 dest_pos++;			
					 i++;
					 
				 }
				 //Loop OutData into Outbuffer 
				 for (int i=0; i<OutData->Length; i++)
				 {
					 OutBuffer[i+2] = OutData[i];	
				 }

				 // Write to endpoint 3.
				 // Buffer to write
				 // Number of bytes to write
				 JM60->WriteData(3, OutBuffer, packetSize );     //Send command/data
				 
				 this->state->increaseInBufferPutterPosition(DataToWrite->Count);	

				 				 

				 // Start the MCU if appropriate to
				if (!this->sentRunning) {
					Thread::Sleep(min_sleep_time_between_cmds);
					this->sendMCURunCommand();
				}
			 }

	// Read the status of the MCU, and sync it with the GUI
	private: int UpdateStatus(System::Void) {
				 cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(255);
				 cli::array<unsigned char> ^ InBuffer = gcnew cli::array<unsigned char>(12);
				 cli::array<unsigned char> ^ InData = gcnew cli::array<unsigned char>(12);

				 OutBuffer[0] = Status;
				 JM60->WriteData(3, OutBuffer, 1);                    //Send command/data			
				 
				 Thread::Sleep(min_sleep_time_between_cmds);
				 
				 JM60->ReadBulkEndpoint(4, InBuffer, 12);   //Read Command/Data

				 array<UInt16>^statusInts = {0, 0, 0, 0, 0, 0};
				 int j = 0;

				 //Loop InBuffer into InData 
				 for (int i=0; i<InBuffer->Length; i = i + 2)
				 {
					 array<unsigned char>^bytes = {InBuffer[i+1], InBuffer[i]};
					 UInt16 value = BitConverter::ToUInt16( bytes, 0 );

					 statusInts[j] = value;
					 j++;
				 }	

				int inBufferPutter     = statusInts[0];
				int inBufferGetter     = statusInts[1];
				int outBufferPutter    = statusInts[2];
				int outBufferGetter    = statusInts[3];
				int errorReceivedCount = statusInts[4];
				int status_code        = statusInts[5];
				
				this->state->setInBufferPutterPosition(inBufferPutter);
				this->state->setInBufferGetterPosition(inBufferGetter);

				this->state->setOutBufferPutterPosition(outBufferPutter);
				this->state->setOutBufferGetterPosition(outBufferGetter);

				this->state->setMCUTxErrorCount(errorReceivedCount);

				return status_code;
			 }

	// Send the Read CMD (and the no. of bytes to read)
	private: void SendReadCmd(int no_to_read) {
			 	cli::array<unsigned char> ^ OutBuffer = gcnew cli::array<unsigned char>(2);
				 
				OutBuffer[0] = Get_Data;
				OutBuffer[1] = no_to_read;
				JM60->WriteData(3, OutBuffer, 2);                    //Send command/data
			 }

	// Read data, returns the number of bytes read.
	private: int GetData(System::Void) {
				 int read_at_most =  ( ( ( ( this->state->getOutBufferUsage() ) * 2) / 12 ) * 12 );

				 // Our read command includes a single byte to represent the no. of bytes to read.
				 if ( read_at_most > 252 ) {
					read_at_most = 252;
				 }

				 // If nothing to read, return 0
				 if ( read_at_most == 0 ) {
					 return 0;
				 }

				 cli::array<unsigned char> ^ InBuffer = gcnew cli::array<unsigned char>(read_at_most);
				 cli::array<unsigned char> ^ InData = gcnew cli::array<unsigned char>(read_at_most);
								 
				 SendReadCmd(read_at_most);
				 Thread::Sleep(min_sleep_time_between_cmds);				 

				 JM60->ReadBulkEndpoint(4, InBuffer, read_at_most);   //Read Command/Data
				 
				 array<UInt16>^ints = {0, 0, 0, 0, 0, 0};
				 int j = 0;

				 //Loop InBuffer into InData 
				 for (int i=0; i<InBuffer->Length; i = i + 2)
				 {
					 InData[i]   = InBuffer[i];
					 InData[i+1] = InBuffer[i+1];

					 array<unsigned char>^bytes = {InBuffer[i+1], InBuffer[i]};
					 UInt16 value = BitConverter::ToUInt16( bytes, 0 );

					 
					 ints[j] = value;
					 
					 j++;

					 if ( j >= 6 ) {
						ReceivedDataRecord^ receivedDataRecord = gcnew ReceivedDataRecord(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5]);

						this->state->addReceivedRecord(receivedDataRecord);						
						
						j = 0;
					 }

				 }	
				 this->state->increaseOutBufferGetterPosition((read_at_most / 2));			
				 return read_at_most;
			 }
	
	// User changed the mode of operation.
	private: System::Void modeOfOperationSelector_SelectedIndexChanged(System::Object^  sender, System::EventArgs^  e) {
				 int selectedIndex = this->modeOfOperationSelector->SelectedIndex;

				 if (selectedIndex == this->modeOfOperationSelector->FindStringExact("Calibration Mode")) {
					 this->state->setCalibrationMode();

					 this->isCalibratedLabel->ForeColor = System::Drawing::Color::Firebrick;
					 this->isCalibratedLabel->Text = L"Not Calibrated";
			
					this->modeOfOperationSelector->Enabled = false;

				 } else {
					this->state->setNormalMode();		 			
				 }				
			 }
	
	// Write the Sent and Received Data to the CSV file.
	private: System::Void exportCSVFileDialog_FileOk(System::Object^  sender, System::ComponentModel::CancelEventArgs^  e) {
			IO::Stream^ stream = this->exportCSVFileDialog->OpenFile();
			if (stream) {
				StreamWriter^ sw = gcnew StreamWriter(stream);
				// Write out the CSV Contents
				this->state->writeToStream(sw);
				sw->Close();
			}
		}

	private: System::Void MainGUI_HelpButtonClicked(System::Object^  sender, System::ComponentModel::CancelEventArgs^  e) {
				 this->showHelp();
			 }

	private: void showHelp() {
				 System::Text::StringBuilder^ messageBoxCS = gcnew System::Text::StringBuilder();
				 messageBoxCS->AppendLine();
				 messageBoxCS->AppendLine("1. Connect the device to the system via the USB port");
				 messageBoxCS->AppendLine("2. Press the 'Connect' button, and ensure that the connection status shows 'Connected'");
				 messageBoxCS->AppendLine("3. Use the 'Data Import' button to select the calibration input CSV file");
				 messageBoxCS->AppendLine("4. Press 'Start'");				 
				 messageBoxCS->AppendLine("5. Wait until the simulation completes, and the system shows 'Calibrated'");
				 messageBoxCS->AppendLine("6. Use the 'Data Import' button to select the simulation input CSV file");
				 messageBoxCS->AppendLine("7. Press 'Reset', and then 'Start' to begin the simulation");
				 messageBoxCS->AppendLine("8. Use the 'Transfer Data Graph' and 'Diagnostics' buttons to monitor the simulation");
				 messageBoxCS->AppendLine("9. Wait until the simulation completes");
				 messageBoxCS->AppendLine("10. Use the 'Data Export' button to export the simulated data results");
				 messageBoxCS->AppendLine();
				 MessageBox::Show(messageBoxCS->ToString(), "Help" );
			 }
};

}